(function () {
    'use strict';

    angular.module('ender.game.arena', ['ngResource', 'ender.game.services']).
        controller('ArenaController', function ($scope, $routeParams, $location, arenaRepository, gameStateService, animationRepository, pathfinderService) {
            var character = gameStateService.getPlayerCharacter();
            var arena;
            if ($routeParams.arenaId) {
                arena = arenaRepository.getArenaById($routeParams.arenaId);
            } else {
                arena = arenaRepository.getRandomArena();
            }

            $scope.safeApply = function(fn) {
                var phase = this.$root.$$phase;
                if (phase == '$apply' || phase == '$digest') {
                    if (fn && (typeof (fn) === 'function')) {
                        fn();
                    }
                } else {
                    this.$apply(fn);
                }
            };

            // TODO for testing only
            arenaRepository.promise.then(function whenArenaLoaded() {
                $scope.arenaBackground = 'url(' + arena.backgroundImage + ')';
                $scope.arenaGrid = arena.grid;
            });

            // TODO for testing only
            animationRepository.promise.then(function whenAnimationsLoaded() {
                arena.placeCharacter(character);
                nextCharacterAction();
            });

            $scope.goToTown = function () {
                $location.path('/town').search({});
            }

            var clickEnabled = true;
            var selectedCharacter = character;

            function unhighlightCells() {
                // deselect character
                var position = selectedCharacter.animation.position;
                $scope.arenaGrid[position.row][position.column].selected = false;
                clearShadows($scope.arenaGrid);
            }

            function clearShadows(arenaGrid) {
                for (var i = 0; i < arenaGrid.length; i++) {
                    for (var j = 0; j < arenaGrid[i].length; j++) {
                        arenaGrid[i][j].chargeable = false;
                        arenaGrid[i][j].attackable = false;
                        arenaGrid[i][j].reachable = false;
                    }
                }
            }

            function moveCharacter(motionPath, successFunction) {
                $scope.$broadcast('animation', 'move', {
                    position: selectedCharacter.position,
                    path: motionPath,
                    successFunction: successFunction
                });
            }

            function updateCharacterPosition(character, destinationRow, destinationColumn) {
                var position = character.animation.position;
                // delete from old cell
                $scope.arenaGrid[position.row][position.column].character = null;
                // place in new cell
                $scope.arenaGrid[destinationRow][destinationColumn].character = character;
                // update position
                position.row = destinationRow;
                position.column = destinationColumn;
            }

            function nextCharacterAction() {
                clickEnabled = false;

                // when animation finishes, the timeouts are outside of the angular world
                $scope.safeApply(function () {
                    // select character
                    var position = selectedCharacter.animation.position;
                    $scope.arenaGrid[position.row][position.column].selected = true;

                    // show reachable zone
                    var reachableNodes = pathfinderService.getReachableEmptyNodes($scope.arenaGrid, position.row, position.column);
                    for (var i = 0; i < reachableNodes.length; i++) {
                        var node = reachableNodes[i];
                        $scope.arenaGrid[node.row][node.column].reachable = true;
                    }

                    // show melee-attackable zone
//                    var characters = arena.getCharacters();
//                    for (var i = 0; i < characters.length; i++) {
//                        var character = characters[i];
//                        var path = getPath(graph, selectedCharacter.position, character.position);
//                        path = pathToMotionPath(path);
//                        var reach = CHARACTER_SPEED; // TODO hardcoded speed
//                        // path also includes start node, hence the "-1"
//                        if (path.length > 0 && path.length - 1 <= reach) {
//
//                            if (path.length == 1) {
//                                $scope.arenaGrid[character.position.row][character.position.column].attackable = {
//                                    direction: path[0]
//                                };
//                            } else
//                                $scope.arenaGrid[character.position.row][character.position.column].chargeable = true;
//                        }
//                    }

                    // enable user interaction
                    clickEnabled = true;
                });
            }

            $scope.cellClicked = function (column, row) {
                if (!clickEnabled)
                    return;
                // move
                if ($scope.arenaGrid[row][column].reachable) {

                    unhighlightCells();

                    var motionPath = pathfinderService.getMotionPath($scope.arenaGrid, selectedCharacter.animation.position, row, column);

                    moveCharacter(motionPath, function () {
                        updateCharacterPosition(selectedCharacter, row, column);
                        nextCharacterAction();
                    });

                }

                // attack
//                if ($scope.arenaGrid[row][column].attackable) {
//                    var attackDirection = $scope.arenaGrid[row][column].attackable.direction;
//                    unhighlightCells();
//                    attackCharacter(attackDirection, $scope.arenaGrid[row][column], function () {
//                        // TODO compute damage
//                        // TODO play defend/die animation for enemy
//                        // TODO check victory
//
//                        nextCharacterAction();
//                    });
//                }

                // charge
//                if ($scope.arenaGrid[row][column].chargeable) {
//
//                    unhighlightCells();
//
//                    var graph = createGraph($scope.arenaGrid);
//                    var path = getPath(graph, selectedCharacter.position, {
//                        row: row,
//                        column: column
//                    });
//                    var motionPath = pathToMotionPath(path);
//                    // do not move over the other character
//                    path.pop();
//                    var lastNode = path[path.length - 1];
//                    var destination = {
//                        row: lastNode.y,
//                        column: lastNode.x
//                    };
//
//                    var attackDirection = motionPath.pop();
//                    moveCharacter(motionPath, function () {
//                        updateCharacterPosition(selectedCharacter, destination.row, destination.column);
//                        $scope.safeDigest(); // FIXME sometimes digest is already in progress and the attack animation does not show
//                        attackCharacter(attackDirection, $scope.arenaGrid[row][column], function () {
//                            // TODO compute damage
//                            // TODO play defend/die animation for enemy
//                            // TODO check victory
//
//                            nextCharacterAction();
//                        });
//                    });
//
//                }
            }
        }
    )
        .factory('pathfinderService', function () {

            function createGraph(grid) {
                var graphGrid = [];
                for (var i = 0; i < grid.length; i++) {
                    graphGrid[i] = [];
                    for (var j = 0; j < grid[i].length; j++) {
                        var cell = grid[i][j];
                        if (cell.character || cell.obstacle)
                            graphGrid[i][j] = 1;
                        else
                            graphGrid[i][j] = 0;
                    }
                }

                function transpose(a) {
                    return Object.keys(a[0]).map(function (c) {
                        return a.map(function (r) {
                            return r[c];
                        });
                    });
                }

                var graph = new Graph(transpose(graphGrid));
                return graph;
            }

            function pathToMotionPath(path) {
                var motionPath = [];
                for (var i = 0; i < path.length - 1; i++) {
                    var from = path[i];
                    var to = path[i + 1];
                    if (from.x - 1 == to.x)
                        motionPath.push('left');
                    if (from.x + 1 == to.x)
                        motionPath.push('right');
                    if (from.y - 1 == to.y)
                        motionPath.push('up');
                    if (from.y + 1 == to.y)
                        motionPath.push('down');
                }
                return motionPath;
            }

            return {
                getMotionPath: function (arenaGrid, startPosition, row, column) {
                    var graph = createGraph(arenaGrid);
                    var graphNodes = graph.nodes;
                    var startNode = graphNodes[startPosition.column][startPosition.row];
                    var path = astar.search(graphNodes, startNode, graphNodes[column][row]);

                    path.unshift(startNode);
                    return pathToMotionPath(path);
                },
                getReachableEmptyNodes: function (arenaGrid, rowIndex, columnIndex) {
                    var graph = createGraph(arenaGrid);
                    var graphNodes = graph.nodes;
                    var radius = 5; // TODO hardcoded speed
                    // arenaGrid[x][y].character.stats.speed + 1;
                    var nodesToBeSearched = [];

                    // gather all nodes in 'speed' radius
                    for (var c = columnIndex - radius; c <= columnIndex + radius && c < graphNodes.length; c++) {
                        var column = graphNodes[c];
                        if (column) {
                            for (var r = rowIndex - radius; r <= rowIndex + radius && r < column.length; r++) {
                                if (graphNodes[c][r]) {
                                    nodesToBeSearched.push(graphNodes[c][r]);
                                }
                            }
                        }
                    }

                    // search all of them
                    var startNode = graphNodes[columnIndex][rowIndex];
                    // x is the column , y is the row
                    var reachableNodesSet = {};
                    for (var i = 0; i < nodesToBeSearched.length; i++) {
                        var node = nodesToBeSearched[i];
                        var path = astar.search(graphNodes, startNode, node);
                        if (path.length === 0 || path.length > radius)
                            continue;
                        for (var j = 0; j < path.length; j++) {
                            var node = path[j];
                            var key = node.x + "." + node.y;
                            if (!reachableNodesSet[key])
                                reachableNodesSet[key] = node;
                        }
                    }

                    var reachableNodesList = [];
                    for (var key in reachableNodesSet) {
                        var node = reachableNodesSet[key];
                        reachableNodesList.push({
                            row: node.y,
                            column: node.x
                        });
                    }

                    return reachableNodesList;
                }

            }
        })
        .factory('arenaRepository', function ($resource, $q) {
            var resource = $resource('data/arenas.json');
            var deferred = $q.defer();
            var arenasResource = resource.get(function () {
                deferred.resolve();
            });

            function generateArena(generatedArena, arenaResource) {
                // TODO arena processing
                generatedArena.width = arenaResource.width;
                generatedArena.height = arenaResource.height;

                generatedArena.backgroundImage = "images/terrain/background/" + arenaResource.background + ".png";
                generatedArena.grid = generateGrid(arenaResource.height, arenaResource.width);

                generatedArena.positions = {
                    player: arenaResource.player,
                    enemy: arenaResource.enemy
                };
            }

            function generateGrid(height, width) {
                var arenaGrid = [];
                for (var i = 0; i < height; i++) {
                    arenaGrid[i] = [];
                    for (var j = 0; j < width; j++)
                        arenaGrid[i][j] = {};
                }
                return arenaGrid;
            }

            return {
                getRandomArena: function () {
                    // TODO getRandomArena
                    return this.getArenaById('concrete-yard');
                },
                getArenaById: function (arenaId) {
                    var generatedArena = new Arena();

                    deferred.promise.then(function () {
                        var arenaResource = arenasResource[arenaId];
                        generateArena(generatedArena, arenaResource);
                    });

                    return generatedArena;
                },
                promise: deferred.promise
            };
        })
        .directive('renderedArenaCell', function () {
            return {
                restrict: 'E',
                replace: true,
                scope: {
                    content: '='
                },
                template: '<div></div>',
                link: function (scope, element, attrs) {

                    scope.$watch('content', function () {
                        if (scope.content) {
                            element.addClass('content-holder');

                            var character = scope.content;
                            var animation = character.animation;
                            var standingDiv = animation.renderStandingCharacter();

                            if (animation.facing == 'right')
                                element.removeClass("flipped");
                            else if (animation.facing == 'left')
                                element.addClass("flipped");

                            element.html(standingDiv);
                        } else {
                            element.removeClass('content-holder');
                            element.html('');
                        }
                    });

                    scope.$on('animation', function (eventData, type, param) {
                        if (scope.content)
                            param.successFunction();
//                        if (scope.content && scope.content instanceof Ender.Game.Classes.Character) {
//                            if (type == 'move' && angular.equals(scope.content.position, param.position)) {
//                                scope.content.animation.startMoveAnimation(element, param.path, scope.content, param.successFunction);
//                            }
//                            if (type == 'attack' && angular.equals(scope.content.position, param.position)) {
//                                scope.content.animation.startAttackAnimation(element, param.direction, scope.content, param.successFunction);
//                            }
//                        }
                    });
                }
            };
        });

    /**
     *
     * @constructor
     */
    function Arena() {

    }

    Arena.prototype.placeCharacter = function (character, position, facing) {
        if (!position) {
            position = this.positions.player;
        }
        var animation = character.animation;
        if (!facing)
            animation.facing = 'right';
        else
            animation.facing = facing;

        this.grid[position.y - 1][position.x - 1].character = character;
        animation.position = {
            column: position.x - 1,
            row: position.y - 1
        };
    };

})
    ();